package ee.ut.system.modeling.hw3;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class PertChart {

	private String name;
	private Set<String> allDestinations = new TreeSet<String>();
	private List<String> pathes = new ArrayList<String>();
	static Map<String, Task> hashTasks = new HashMap<String, Task>();

	public PertChart(String name) {
		super();
		this.name = name;

	}

	public static void main(String[] args) throws IOException {
		String filename = checkInputParametersAndGetFileName(args);
		PertChart chart = new PertChart(filename);
		try {
			chart.parseFile(filename);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private static String checkInputParametersAndGetFileName(String[] args)
			throws IOException {
		if (args.length != 1) {
			throw new IOException("Maximum 1 argument: PERT file name");
		}
		return args[0];
	}

	public void parseFile(String fileName) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader(fileName));
		List<Task> tasks = new ArrayList<Task>();
		try {
			StringBuilder sb = new StringBuilder();
			String line = br.readLine();
			boolean firstLine = true;

			while (line != null) {
				line = line.replaceAll(" ", "");
				sb.append(line);
				sb.append('\n');
				if (validateLine(line)) {
					if (checkDestinationValidness(line, firstLine)) {
						tasks.add(createTask(line));
						firstLine = false;
					} else {
						throw new IOException(
								"Invalid PERT file: unknown task!");
					}
				} else {
					throw new IOException(
							"Invalid PERT file: invalid file structure!");
				}
				line = br.readLine();
			}
		} finally {
			br.close();
		}
		tasks = calculateCriticalPath(tasks);
		DotFile dt = new DotFile(fileName,tasks);
		dt.generateStructure();

	}

	private boolean validateLine(String line) {
		boolean valid = false;
		String[] splitedLine = line.split(",");
		if (splitedLine.length >= 2) {
			if (isInteger(splitedLine[1])) {
				valid = true;
			}
		}
		return valid;
	}

	private boolean checkDestinationValidness(String line, boolean firstLine) {
		boolean valid = false;
		String[] splitedLine = line.split(",");
		String id = splitedLine[0];
		List<String> destinations = Arrays.asList(Arrays.copyOfRange(
				splitedLine, 2, splitedLine.length));
		for (String s : destinations) {
			allDestinations.add(s);
		}

		if (firstLine) {
			allDestinations.add(id);
		}

		if (allDestinations.contains(id)) {
			valid = true;
		}
		return valid;
	}

	private Task createTask(String line) {
		String[] splitedLine = line.split(",");
		String id = splitedLine[0];
		int duration = Integer.parseInt(splitedLine[1]);
		List<String> destinations = Arrays.asList(Arrays.copyOfRange(
				splitedLine, 2, splitedLine.length));
		List<Dependency> dependencies = createDependencies(id, destinations);
		Task task = new Task(id, duration, false, dependencies);
		return task;
	}

	private List<Dependency> createDependencies(String id, List<String> destinations) {
		List<Dependency> dependencies = new ArrayList<Dependency>();
		for (String s : destinations) {
      Dependency d = new Dependency(s, false, "'" + id + "-" + s + "'");
			dependencies.add(d);
    }
		return dependencies;
	}

	private boolean isInteger(String s) {
		try {
			Integer.parseInt(s);
		} catch (NumberFormatException e) {
			return false;
		}
		return true;
	}

	public List<Task> calculateCriticalPath(List<Task> tasks) {
		String path = "start";
		tasks.get(0).setCriticalPath(true);
		tasks.get(tasks.size() - 1).setCriticalPath(true);
		hashTasks = generateTaskGraph(tasks);
		pathes.add(path);
		buildCriticalPath(tasks.get(0), 0, path);
		System.gc();
		int weight = 0;
		for (String pathe : pathes) {
			int tempWeight = 0;
			String[] splitedPath = pathe.split(" ");
			for (String point : splitedPath) {
				tempWeight += hashTasks.get(point).getDuration();
			}

			if (tempWeight > weight) {
				weight = tempWeight;
				path = pathe;
			}

		}

		List<String> convertablePath = new ArrayList<String>(Arrays.asList(path
				.split(" ")));
		for (int i = 0; i < tasks.size(); i++) {
			Task task = tasks.get(i);
			if (convertablePath.contains(task.getId())) {
				task.setCriticalPath(true);
				for (Dependency dp : task.getDependencies()) {
					if (convertablePath.contains(dp.getOutgoingDestination())) {
						dp.setCritical(true);
					}
				}
			}

		}

		return tasks;
	}

	public void buildCriticalPath(Task task, int pathIndex, String criticalPath) {
		List<Dependency> dependencies = task.getDependencies();
		if (dependencies.size() > 1) {
			String chainPath = new String(criticalPath);
			for (int i = 0; i < dependencies.size(); i++) {
				Dependency dp = dependencies.get(i);
				if (i == 0) {
					criticalPath += " " + dp.getOutgoingDestination();
					pathes.set(pathIndex, criticalPath);
					buildCriticalPath(
							hashTasks.get(dp.getOutgoingDestination()),
							pathes.indexOf(criticalPath), criticalPath);
				} else {
					String newPathChain = new String(chainPath);
					newPathChain += " " + dp.getOutgoingDestination();
					pathes.add(newPathChain);
					buildCriticalPath(
							hashTasks.get(dp.getOutgoingDestination()),
							pathes.indexOf(newPathChain), newPathChain);
				}

			}
		} else {
			if (task.getId().equals("finish")) {
				return;

			} else {
				Dependency dp = task.getDependencies().get(0);
				criticalPath += " " + dp.getOutgoingDestination();
				pathes.set(pathIndex, criticalPath);
				buildCriticalPath(hashTasks.get(dp.getOutgoingDestination()),
						pathes.indexOf(criticalPath), criticalPath);
			}

		}
	}

	public Map<String, Task> generateTaskGraph(List<Task> tasks) {
		Map<String, Task> hashTasks = new HashMap<String, Task>();
		for (Task task : tasks) {
			hashTasks.put(task.getId(), task);
		}
		return hashTasks;

	}
}
